<template>
  <n-grid :x-gap="12" :y-gap="8" responsive="screen" item-responsive>
    <n-gi span="12 m:8 l:6 xl:4" v-for="list,name in groups" :key="'groups_' + name">
        <n-card 
            class="client-card">
            <template #header>
              <span class="shadow-click" @click="showGroup(name, list)">{{ name }}
                <n-icon class="add-i shadow-click" @click.stop="showForm(false, {name: name})"><AddBoxOutlined/></n-icon>
              </span> 
            </template>

            <n-scrollbar style="height: 14em;cursor:pointer;" @click="showGroup(name, list)">
              <div v-for="item,index in list" :key="'group_' + name+'_'+ index"
                  class="group_wrapper">
                <div class="group-info">
                  <span class="address" v-if="item.sourceClientNode">
                    {{ item.sourceClientNode.name }} {{ item.sourceClientNode.host }}:{{ item.sourceClientNode.port }}
                  </span>
                  <template v-else>*</template>
                  <div><n-ellipsis :line-clamp="1" :title="item.source">{{ item.source }}</n-ellipsis></div>
                </div>

                <div class="info-divider"></div>

                <div class="group-info">
                  <span class="address" v-if="item.targetClientNode">
                    {{ item.targetClientNode.name }} ({{ item.targetClientNode.host }}:{{ item.targetClientNode.port }})
                  </span>
                  <template v-else>*</template>
                  <div><n-ellipsis :line-clamp="1" :title="item.target">{{ item.target }}</n-ellipsis></div>
                </div>
              </div>
            </n-scrollbar>

        </n-card>
    </n-gi>
  </n-grid>

  <!-- 组映射详细信息弹窗 -->
  <n-modal
    v-model:show="showModal"
    class=""
    preset="dialog"
    :title="modalGroupName"
    :bordered="false"
    :segmented="{content: 'soft', footer: 'soft'}"
    :show-icon="false"
    :maskClosable="false"
    :style="{ width: '50em' }"
  >
    <n-scrollbar style="height: 20em">
      <div v-for="item,index in modalGroups" :key="'modalgroup_' + modalGroupName+'_'+ index"
          class="modal_wrapper">

        <div class="modal-info">
          <template v-if="item.sourceClientNode">
            <span class="name"> {{ item.sourceClientNode.name }} </span> &nbsp;
            <span class="addr "> {{ item.sourceClientNode.host }}:{{ item.sourceClientNode.port }} </span>
          </template>
          <template v-else> * </template>

          <div>{{ item.source }}</div>
        </div>

        <div class="center-item shadow-click" title="Synchronize Now" style="font-size: 1.5em; color:green"> 
          <n-icon @click="comformSync(item)"><KeyboardDoubleArrowRightOutlined/></n-icon>
        </div>

        <div class="modal-info">
          <template v-if="item.targetClientNode">
            <span class="name"> {{ item.targetClientNode.name }} </span> &nbsp;
            <span class="addr"> {{ item.targetClientNode.host }}:{{ item.targetClientNode.port }} </span>
          </template>
          <template v-else> * </template>

          <div>{{ item.target }}</div>
        </div>

        <div class="center-item"> 
          <n-button size="small" tertiary type="warning" @click="showForm(true, item)"> 
            <n-icon><ModeEditFilled/></n-icon> 
          </n-button>
          <n-button size="small" tertiary type="error" @click="handleDelete(item)"> 
            <n-icon><DeleteRound/> </n-icon> 
          </n-button>
        </div>
      </div>
    </n-scrollbar>
  </n-modal>

  <!-- 添加和编辑弹窗 -->
  <n-modal
    v-model:show="showFormModal"
    preset="dialog"
    :bordered="false"
    :segmented="{content: 'soft', footer: 'soft'}"
    :show-icon="false"
    :maskClosable="false"
    :style="{ width: '40em' }"
    >
    <template #header>
      <span> <!--{{ isUpdate ? '*':'.' }} -->  &nbsp;  </span>
    </template>

    <n-form     
      label-placement="left"
      label-width="9em"
      ref="formRef" :model="formData" :rules="rules"
      >
      <n-form-item path="name" label="名称">
        <n-input v-model:value="formData.name" placeholder="名称" />
      </n-form-item>
      <n-form-item path="sourceClient" label="源 Clien" style="display:none">
        <n-input v-model:value="formData.sourceClient" placeholder="源 Client" />
      </n-form-item>
      <!--
      <n-form-item path="source" label="源路径">
        <n-input v-model:value="formData.source" placeholder="源路径" />
      </n-form-item>-->
      <n-form-item path="source" label="源路径">
        <n-input-group>
          <n-input :style="{ width: '50%' }" v-model:value="formData.source" />
          <n-button type="primary" ghost @click="showServerPathSelector()"> 选择 </n-button>
        </n-input-group>
      </n-form-item>
      <n-form-item path="targetClient" label="目标 Client">
        <n-input-group>
          <n-input :style="{ width: '50%' }" readonly v-model:value="formData.targetClientNode.name" />
          <n-button type="primary" ghost @click="showClientSelector()"> 选择 </n-button>
        </n-input-group>
      </n-form-item>
      <n-form-item path="target" label="目标路径">
        <n-input-group>
          <n-input :style="{ width: '50%' }" v-model:value="formData.target" />
          <n-button type="primary" ghost @click="showClientPathSelector()"> 选择 </n-button>
        </n-input-group>
      </n-form-item>
      <n-form-item path="mark" label="Remark">
        <n-input type="textarea" :rows="3" v-model:value="formData.mark" placeholder="" />
      </n-form-item>
      
      <!-- 仅存在Client上的文件： -->
      <n-form-item path="fileOnlyOnClient" label="仅存在Client上的文件（预设）">
        <n-radio-group v-model:value="formData.fileOnlyOnClient" name="">
          <n-radio :value="0">不处理</n-radio>
          <n-radio :value="1">删除</n-radio>
        </n-radio-group>
      </n-form-item>

      <div style="font-size: 0.8em"> 
        <p class="section">文件重名规则对无变化的文件无效。（无变化判定： server文件 与 client 文件修改时间不一致）</p>
      </div>
      <n-form-item path="cover" label="文件重名（预设）">
        <n-radio-group v-model:value="formData.cover" name="文件重名">
          <n-radio :value="1">覆盖</n-radio>
          <n-radio :value="0">保留</n-radio>
        </n-radio-group>
      </n-form-item>

      <div v-if="formData.fileOnlyOnClient && !formData.cover" class="warnning-text">“删除” + “保留” 无意义！</div>

      <n-form-item path="mark" label="重写路径（预设）">
        <n-input type="text" v-model:value="formData.rewritePath" size="tiny" placeholder=":/?-{yyyy-MM-dd HH:mm:ss}" />
      </n-form-item>
      <div style="font-size: 0.8em"> 
        <label style="color: black;font-weight:600">重写路径规则：</label>
        <p class="section">
          必须以 ':' 开头， ':' 代表 {{ formData.target ? formData.target : '根路径'}}。<br/>
          '?'代表原文件相对 {{ formData.source ? formData.source : '源根路径'}} 的子路径。 <br/>
          可以使用时间占位符 '{yyyy-MM-dd HH-mm-ss}' （即 java 格式化时间）<br/>
          <!-- 新建文件夹可以使用 '/' 。<br/> -->
          <span class="impl">注意：文件名不能出现 :? 等字符！</span><br>
          例如，  :/{yyyy-MM-dd}/? &nbsp;&nbsp; :/{yyyy-MM-dd} {HH-mm}/? &nbsp;&nbsp; :/?-{yyyy-MM-dd HH-mm-ss}
        </p>
      </div>

      
      <div style="display: flex; justify-content: flex-end">
        <n-button round type="primary"  style="margin-right:4px"
          @click="handleSubmit()">
          {{ isUpdate ? '更新' : '保存' }}
        </n-button>
        
        <n-button round type="" @click="showFormModal=false" >
          关闭
        </n-button>
      </div>
    </n-form>
  </n-modal>

  <!-- 单路径同步 -->
  <SyncModal ref="syncModalRef"></SyncModal>
  <!-- client 选择 -->
  <ClientSelector ref="clientSelectorRef" @selected="clientSelected"></ClientSelector>
  <!-- client 路径选择 -->
  <ClientPathSelector ref="clientPathSelectorRef" @selected="clientPathSelected"></ClientPathSelector>
  <!-- server 路径选择 -->
  <ServerPathSelector ref="serverPathSelectorRef" @selected="serverPathSelected"></ServerPathSelector>
</template>

<script setup>
import { ref, inject, onMounted } from 'vue'
import { KeyboardDoubleArrowRightOutlined, ModeEditFilled, DeleteRound, AddBoxOutlined  } from '@vicons/material'
import serverApi from '/src/api/fsync/server.js'
import SyncModal from './sync-modal.vue'
import ClientSelector from './client-selector.vue'
import ClientPathSelector from './client-path-selector.vue'
import ServerPathSelector from './server-path-selector.vue'

const groups = ref({})   // { videos: [{}] }
const showModal = ref(false)

const modalGroupName = ref('')
const modalGroups = ref([])
const syncModalRef = ref(null)

const clientSelectorRef = ref(null)
const clientPathSelectorRef = ref(null)
const serverPathSelectorRef = ref(null)

const showFormModal = ref(false)
const formRef = ref(null) // 表单组件
const isUpdate = ref(false) // 表单是更新
const formData = ref({}) // 表单数据
const formLoading = ref(false)
const rules = ref({
  name: [ { required: true, message: 'name required', trigger: 'blur' } ],
  // sourceClient: [ { required: true, message: 'sourceClient required', trigger: 'blur' } ],
  source: [ { required: true, message: 'source required', trigger: 'change' } ],
  targetClient: [ { key: "targetClient", trigger: 'change', validator (rule, value,) { // 自动验证失效
      if (!value) {
        return new Error('targetClient required')
      }
      return true
    }, 
  }],
  target: [ { required: true, message: 'target required', trigger: 'change' } ],
})
// 手动触发 targetClient 的验证
const validateTargetClient = () => {
  formRef.value?.validate( (errors) => {
    if (errors) {
      console.error(errors)
    }
  }, (rule) => {
    return rule?.key === 'targetClient'
  })
}


const Message = inject("Message")
const Dialog = inject("Dialog")

const initGroupMap = (groupMap) => {
  return {
    id: groupMap?.id || null,
    name: groupMap?.name || null, 
    sourceClient: groupMap?.sourceClient || null, 
    source: groupMap?.source || null, 
    sourceClientNode: groupMap?.sourceClientNode || {},  // vo 字段
    targetClient: groupMap?.targetClient || null, 
    targetClientNode: groupMap?.targetClientNode || {}, // vo 字段
    target: groupMap?.target || null, 
    mark: groupMap?.mark || null,

    cover: groupMap?.cover || 0,
    rewritePath: groupMap?.rewritePath || null,
    fileOnlyOnClient: groupMap?.fileOnlyOnClient || 0,
  }
}

// 列出所有的 group 
const refreshList = async () => {
  groups.value = {}
  await listGroupNames()
}
const listGroupNames = async () => {
  await serverApi.groupNames().then(async (res) => {
      let names = res.data
      for(let i=0; i<names.length; i++) {
        await listGroupMap(names[i])
      }
  }, err => {
    Message.error( err.message, { closable: true, duration: 3000 } )
    console.error(err)
  })
} 
const listGroupMap = async (name) => {
  await serverApi.groupList({name}).then(res => {
    groups.value[name] = res.data
  }, err => {
    Message.error( err.message, { closable: true, duration: 3000 } )
    console.error(err)
  })
}

const testGroups = () => {
  groups.value["留影"] = [ 
    {
      id: 1, name: "留影", sourceClient: 1, source: 'D:\\home\\caibai\\test01', targetClient: 2, target: '/home/site/log', mark:'', 
      sourceClientNode: { id: 1, name: '章', protocol: 'http', host: 'localhost', port: 22 }, 
      targetClientNode: { id: 2, name: '前', protocol: 'http', host: 'localhost', port: 8052 },
    },
    {
      id: 2, name: "留影2", sourceClient: 1, source: 'D:\\home\\caibai\\test02', targetClient: 2, target: '/home/site/log', mark:'', 
      sourceClientNode: { id: 1, name: '章', protocol: 'http', host: 'localhost', port: 22 }, 
      targetClientNode: { id: 2, name: '前', protocol: 'http', host: 'localhost', port: 8052 },
    },
    {
      id: 3, name: "留影3", sourceClient: 1, source: 'D:\\home\\caibai\\test03', targetClient: 2, target: '/home/site/log', mark:'', 
      sourceClientNode: { id: 1, name: '章', protocol: 'http', host: 'localhost', port: 22 }, 
      targetClientNode: { id: 2, name: '前', protocol: 'http', host: 'localhost', port: 8052 },
    },
  ]
}

// 弹窗显示某个 groups 所有信息
const showGroupByName = (name) => {
  let list = groups.value[name]
  if(!list) {
    closeModal()
  } else {
    showGroup(name, list)
  }
}
const showGroup = (name, list) => {
  modalGroupName.value = name
  modalGroups.value = list
  showModal.value = true
}
const closeModal = () => {
  modalGroupName.value = ''
  modalGroups.value = []
  showModal.value = false
}

// 弹窗，编辑或添加
// showForm(true, vo)  showForm(false, null) 
const showForm = (isUpdateOrNot, groupMap) => {
  isUpdate.value = isUpdateOrNot
  formData.value = initGroupMap(groupMap)
  showFormModal.value = true
}
const closeForm = () => {
  showFormModal.value = false
}
// 提交表单
const handleSubmit = () => {
  // 更新或添加
  formRef.value.validate((errors) => {
    if(errors) {
      console.error(errors)
      return
    }

  // 更新或添加
  let reqMethod = isUpdate.value ? serverApi.groupUpdate : serverApi.groupAdd
  let data = JSON.parse(JSON.stringify(formData.value))
  // if(Object.keys(data.sourceClientNode).length == 0) {
    // data.sourceClientNode = null
  // }
  // if(Object.keys(data.targetClientNode).length == 0) {
    // data.targetClientNode = null
  // }
  // 去除这两个参数
  delete data.sourceClientNode
  delete data.targetClientNode

  formLoading.value = true
  reqMethod(data).then(async (res) => {
      // console.log("add-client-res", res)
      formLoading.value = false
      closeForm()
      await listGroupNames()
      showGroupByName(modalGroupName.value)
    }, err => {
      formLoading.value = false
      Message.error( err.message, { closable: true, duration: 3000 } )
      console.error(err)
    })
  })
}
// 删除
const handleDelete = async (groupMap) => {
  Dialog.warning({
    title: '删除',
    content: '确认要删除这个 GroupMap ？',
    positiveText: '删除',
    negativeText: '取消',
    maskClosable: false,
    onPositiveClick: () => {
      serverApi.groupDel(groupMap.id).then(res => {
        refreshList()
        showGroupByName(modalGroupName.value.name)
      }, err => {
        formLoading.value = false
        Message.error( err.message, { closable: true, duration: 3000 } )
        console.error(err)
      })
    },
  })
}
// 选中 client
const clientSelected = (selectedNode) => {
  formData.value.targetClientNode = selectedNode
  formData.value.targetClient = selectedNode.id
  // console.log(selectedNode, formData.value.targetClientNode, formData.value.targetClient)
}
// 选中 client-path
const clientPathSelected = (fileInfo) => {
  formData.value.target = fileInfo.path
}
// 选中 server-path
const serverPathSelected = (fileInfo) => {
  formData.value.source = fileInfo.path
  
  // 设置 rewritePath 为源路径的最后一级（不行，不能判断源是文件夹还是文件）
  // let source = fileInfo.path
  // let lastIndex = source.lastIndexOf("\\")
  // lastIndex = lastIndex != -1 ? lastIndex : source.lastIndexOf("/")
  // if(lastIndex == -1 || lastIndex==source.length) {
  //   return
  // }
  // formData.value.rewritePath = ":/"+source.substring(lastIndex+1)
}

const addGroupMap = () => {
  showForm(false, null)
}

const comformSync = (groupMap) => {
  syncModalRef.value.show(groupMap)
}

// 显示 Client 选择组件
const showClientSelector = () => {
  clientSelectorRef.value.show()
}
// 显示 client-path 选择组件
const showClientPathSelector = () => {
  validateTargetClient()

  let id = formData.value?.targetClientNode?.id
  if(id || id ===0 ) {
    let path = formData.value.target ? formData.value.target : null
    clientPathSelectorRef.value.show(id, path)
  }
}
// 显示 server-path 选择组件
const showServerPathSelector = () => {
  let path = formData.value.source ? formData.value.source : null
  serverPathSelectorRef.value.show(path)
}

// testGroups()
onMounted(() => {
  refreshList()
})

defineExpose({
  addGroupMap,
})

</script>

<style scoped lang="scss">
.client-card{
  position: relative;
}
.address {
  color: #808080;
}
.group_wrapper{
  margin-bottom: 1em;
  padding: 4px;
  border-radius: 4px;
  border: 1px solid #efeff5; 
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);

  .group-info{
    text-align: left;
  }

  &:nth-child(n+1) {
    /*border-bottom: 1px dashed #0a6eab; */
  }
}

.center-item{
  display: flex;
  align-items: center;

  button{
    margin-right: 4px;
  }
}

.modal_wrapper{
  text-align: center;
  margin: 1em 0em;
  display: flex;

  border-radius: 4px;
  border: 1px solid #efeff5; 
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  
  .modal-info{
    flex: 1;

    .name{
      font-size: 1rem;
    }
    .addr{
      color: gray;
    }
  }
}

.add-i{
  visibility: hidden;
}
.shadow-click{
  &:hover {
    .add-i{
      visibility: visible;
      //display: inline;
    }
  }
}

.info-divider{
  height: 1px;
  border-top: 1px dashed #b6c6cf;
}

.section{
  margin: 0.5em 0.2em;
  color: gray;
}
.impl{
  color: black;
}
</style>
